﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

namespace ShiQuan.SmartDevice
{
    ///<summary>
    /// ATAPI驱动器相关
    ///</summary>
    public class AtapiDevice
    {
        const uint DFP_GET_VERSION = 0x00074080;

        const uint DFP_SEND_DRIVE_COMMAND = 0x0007c084;

        const uint DFP_RECEIVE_DRIVE_DATA = 0x0007c088;

        const uint GENERIC_READ = 0x80000000;

        const uint GENERIC_WRITE = 0x40000000;

        const uint FILE_SHARE_READ = 0x00000001;

        const uint FILE_SHARE_WRITE = 0x00000002;

        const uint CREATE_NEW = 1;

        const uint OPEN_EXISTING = 3;

        
        #region GetHddInfo

 

        ///<summary>
        /// 获得硬盘信息
        ///</summary>
        ///<param name="driveIndex">硬盘序号</param>
        ///<returns>硬盘信息</returns>
        ///<remarks>
        /// 参考lu0的文章：http://lu0s1.3322.org/App/2k1103.html
        /// bysunmast for everyone
        ///thanks lu0 for his great works
        /// 在Windows98/ME中，S.M.A.R.T并不缺省安装，请将SMARTVSD.VXD拷贝到%SYSTEM%\IOSUBSYS目录下。
        /// 在Windows2000/2003下，需要Administrators组的权限。
        /// </remarks>
        ///<example>
        ///AtapiDevice.GetHddInfo()
        ///</example>
        public static HardDiskInfo GetHddInfo(byte driveIndex)
        {
            //操作系统版本
            switch (Environment.OSVersion.Platform)
            {
               case PlatformID.Win32Windows:
                   return GetHddInfo9x(driveIndex);
               case PlatformID.Win32NT:
                   return GetHddInfoNT(driveIndex);
               case PlatformID.Win32S:
                   throw new NotSupportedException("Win32s is not supported.");
               case PlatformID.WinCE:
                   throw new NotSupportedException("WinCE is not supported.");
               default:
                   throw new NotSupportedException("Unknown Platform.");
            }
        }
        #region GetHddInfo9x

        private static HardDiskInfo GetHddInfo9x(byte driveIndex)
        {
           GetVersionOutParams vers = new GetVersionOutParams();

           SendCmdInParams inParam = new SendCmdInParams();

           SendCmdOutParams outParam = new SendCmdOutParams();

            uint bytesReturned = 0;

           IntPtr hDevice = WinKernel32Helper.CreateFile(@"\\.\Smartvsd", 0,0,IntPtr.Zero,CREATE_NEW,
               0,IntPtr.Zero);

            if(hDevice == IntPtr.Zero)
            {
               throw new Exception("Open smartvsd.vxd failed.");
            }

            if (0 == WinKernel32Helper.DeviceIoControl( hDevice,
               DFP_GET_VERSION,IntPtr.Zero,0,
               ref vers,(uint)Marshal.SizeOf(vers),
               ref bytesReturned,IntPtr.Zero))
            {
                WinKernel32Helper.CloseHandle(hDevice);
               throw new Exception("DeviceIoControl failed:DFP_GET_VERSION");
            }
            //If IDE identify command not supported, fails
            if (0== (vers.fCapabilities & 1))
            {
                WinKernel32Helper.CloseHandle(hDevice);
               throw new Exception("Error: IDE identify command notsupported.");
            }

            if(0 != (driveIndex & 1))
            {
                inParam.irDriveRegs.bDriveHeadReg = 0xb0;
            }
            else
            {
               inParam.irDriveRegs.bDriveHeadReg = 0xa0;
            }

            if(0 != (vers.fCapabilities & (16 >> driveIndex)))
            {
                // We don't detect a ATAPI device.
                WinKernel32Helper.CloseHandle(hDevice);
               throw new Exception(string.Format("Drive {0} is a ATAPI device, wedon't detect it", driveIndex + 1));

            }
            else
            {
               inParam.irDriveRegs.bCommandReg = 0xec;
            }
           inParam.bDriveNumber = driveIndex;
           inParam.irDriveRegs.bSectorCountReg = 1;
           inParam.irDriveRegs.bSectorNumberReg = 1;
           inParam.cBufferSize = 512;

            if(0 == WinKernel32Helper.DeviceIoControl(
               hDevice,
               DFP_RECEIVE_DRIVE_DATA,
               ref inParam,
               (uint)Marshal.SizeOf(inParam),
               ref outParam,
               (uint)Marshal.SizeOf(outParam),
               ref bytesReturned,
               IntPtr.Zero))
            {
                WinKernel32Helper.CloseHandle(hDevice);
                throw new Exception("DeviceIoControl failed:DFP_RECEIVE_DRIVE_DATA");
            }

           WinKernel32Helper.CloseHandle(hDevice);
           return GetHardDiskInfo(outParam.bBuffer);

        }

       #endregion

        #region GetHddInfoNT


        private static HardDiskInfo GetHddInfoNT(byte driveIndex)
        {

           GetVersionOutParams vers = new GetVersionOutParams();

           SendCmdInParams inParam = new SendCmdInParams();

           SendCmdOutParams outParam = new SendCmdOutParams();

            uint bytesReturned = 0;
            //We start in NT/Win2000
           IntPtr hDevice = WinKernel32Helper.CreateFile(
               string.Format(@"\\.\PhysicalDrive{0}", driveIndex),
               GENERIC_READ | GENERIC_WRITE,
               FILE_SHARE_READ | FILE_SHARE_WRITE,
               IntPtr.Zero,
               OPEN_EXISTING,
               0,
               IntPtr.Zero);

            if(hDevice == IntPtr.Zero)
            {
               throw new Exception("CreateFile faild.");
            }

            if(0 == WinKernel32Helper.DeviceIoControl(
               hDevice,
               DFP_GET_VERSION,
               IntPtr.Zero,
               0,
               ref vers,
               (uint)Marshal.SizeOf(vers),
               ref bytesReturned,
               IntPtr.Zero))
            {
                WinKernel32Helper.CloseHandle(hDevice);
                throw new Exception(string.Format("Drive {0} may not exists.",driveIndex + 1));
            }

            //If IDE identify command not supported, fails
            if(0 == (vers.fCapabilities & 1))
            {
                WinKernel32Helper.CloseHandle(hDevice);
               throw new Exception("Error: IDE identify command notsupported.");
            }

            //Identify the IDE drives
            if(0 != (driveIndex & 1))
            {
               inParam.irDriveRegs.bDriveHeadReg = 0xb0;
            }
            else
            {
               inParam.irDriveRegs.bDriveHeadReg = 0xa0;
            }

            if(0 != (vers.fCapabilities & (16 >> driveIndex)))
            {
                // We don't detect a ATAPI device.
                WinKernel32Helper.CloseHandle(hDevice);

               throw new Exception(string.Format("Drive {0} is a ATAPI device, wedon't detect it.", driveIndex + 1));
            }
            else
            {
               inParam.irDriveRegs.bCommandReg = 0xec;
            }

           inParam.bDriveNumber = driveIndex;

           inParam.irDriveRegs.bSectorCountReg = 1;

           inParam.irDriveRegs.bSectorNumberReg = 1;

           inParam.cBufferSize = 512;

            if(0 == WinKernel32Helper.DeviceIoControl(
               hDevice,
               DFP_RECEIVE_DRIVE_DATA,
               ref inParam,
               (uint)Marshal.SizeOf(inParam),
               ref outParam,
               (uint)Marshal.SizeOf(outParam),
               ref bytesReturned,
               IntPtr.Zero))
            {
                WinKernel32Helper.CloseHandle(hDevice);
               throw new Exception("DeviceIoControl failed:DFP_RECEIVE_DRIVE_DATA");
            }

            WinKernel32Helper.CloseHandle(hDevice);

           return GetHardDiskInfo(outParam.bBuffer);

        }
       #endregion

        private static HardDiskInfo GetHardDiskInfo(IdSector phdinfo)
        {
           HardDiskInfo hddInfo = new HardDiskInfo();

           ChangeByteOrder(phdinfo.sModelNumber);
           hddInfo.ModuleNumber =Encoding.ASCII.GetString(phdinfo.sModelNumber).Trim();

           ChangeByteOrder(phdinfo.sFirmwareRev);
           hddInfo.Firmware =Encoding.ASCII.GetString(phdinfo.sFirmwareRev).Trim();

           ChangeByteOrder(phdinfo.sSerialNumber);
           hddInfo.SerialNumber =Encoding.ASCII.GetString(phdinfo.sSerialNumber).Trim();

           hddInfo.Capacity = phdinfo.ulTotalAddressableSectors / 2 / 1024;

           return hddInfo;
        }

        private static void ChangeByteOrder(byte[] charArray)
        {
            byte temp;
            for(int i = 0; i < charArray.Length; i += 2)
            {
               temp = charArray[i];
               charArray[i] = charArray[i + 1];
               charArray[i + 1] = temp;
            }
        }
       #endregion

    }
}
